#ifndef ABSTRACTENVIRONMENT_H_
#define ABSTRACTENVIRONMENT_H_

#include <QThread>
#include <QVariant>

class Generation;
class SelectionStrategy;
class Chromosome;
class AbstractChromosomeFactory;

/**
  * Enumeration with all methods of removing duplicated chromosomes in population
  */
enum RemoveDuplicatesMethods
{
    /**
      * Switch off removing duplicates
      */
    None,
    /**
      * Duplicates will be mutated
      */
    Mutate,
    /**
      * Duplicates will be replaced by new chromosomes
      */
    Replace
};

/**
  * Enumeration with all finish condition types
  */
enum FinishConditionTypes
{
    /**
      * Simulations ends after reaching specified amound of generations
      */
    GenerationCount,
    /**
      * Simulation ends when chromsome with specified fitness found
      */
    BestFitnessKnown
};

/**
  * Determines default cross over probability
  */
const double DEFAULT_CROSSOVER_PROB = 0.5;

/**
  * Determines default mutation probability
  */
const double DEFAULT_MUTATION_PROB = 0.01;

#ifdef LOG_TO_FILE
#include <QFile>
#include <QTextStream>
#endif

/**
  * Class that represents abstract environment
  * @author Łukasz Szybka
  */
class AbstractEnvironment : public QThread
{
    Q_OBJECT

public:
    /**
      * Destructor
      */
    virtual ~AbstractEnvironment();
    /**
      * Sets AbstractEnvironment::selectionStrategy
      */
    void setSelectionStrategy(SelectionStrategy* _selectionStrategy);
    /**
      * Sets AbstractEnvironment::crossoverProb
      */
    void setCrossoverProb(double _crossoverProb);
    /**
      * Sets AbstractEnvironment::mutationProb
      */
    void setMutationProb(double _mutationProb);
    /**
      * Sets AbstractEnvironment::additionalOperatorProb1
      */
    void setAdditionalOperatorProb1(double _additionalOperatorProb1);
    /**
      * Sets AbstractEnvironment::additionalOperatorProb2
      */
    void setAdditionalOperatorProb2(double _additionalOperatorProb2);
    /**
      * Sets AbstractEnvironment::abortWorseGenerations
      */
    void setAbortWorseGenerations(bool _abortWorseGenerations);
    /**
      * Sets AbstractEnvironment::replaceWorstRepresentative
      */
    void setReplaceWorstRepresentative(bool _replaceWorstRepresentative);
    /**
      * Sets AbstractEnvironment::addFreshChromosomes
      */
    void setAddFreshChromosomes(int _addFreshChromosomes);
    /**
      * Sets AbstractEnvironment::removeDuplicatesMethod
      */
    void setRemoveDuplicatesMethod(int _removeDuplicatesMethod);
    /**
      * Sets AbstractEnvironment::maxGenerationsCount
      */
    void setMaxGenerationsCount(int _maxGenerationsCount);
    /**
      * Sets AbstractEnvironment::adjustFitness
      */
    void setAdjustFitness(bool _adjustFitness);
    /**
      * Sets AbstractEnvironment::finishConditionType
      */
    void setFinishConditionType(int _finishConditionType);
    /**
      * Sets AbstractEnvironment::bestFitness
      */
    void setBestFitness(double _bestFitness);
    /**
      * Sets AbstractEnvironment::populationSize
      */
    void setPopulationSize(int _populationSize);
    /**
      * Sets AbstractEnvironment::emitFrequency
      */
    void setEmitFrequency(int _emitFrequency);
    /**
      * Changes environment behavior to find chromosome with smallest fitness
      * Useful for some problems where chromosome with smallest fitness is best solution
      * Works only with Tournament selection
      * Won't work with Deterministic and RouletteWheel selection
      * @param _minimize If true environment will look for chromosome with smallest fitness
      */
    void setMinimizeFitness(bool _minimize = true);
    /**
      * Creates primary generation and starts simulation.
      */
    void startSimulation();
    /**
      * Decodes chromosome to human-readable form
      * Default implementation calls Chromosome::toString()
      * @param chrom Reference to chromosome that should be decoded
      * @return String containing readable form of chromosome data
      */
    virtual QString decodeChromosome(const Chromosome& chrom) const;

    /**
      * Returns solution
      * @return AbstractEnvironment::solution - Chromosome pointer
      * that should be copied using
      * Chromosome::duplicate() if will be used outside of environment
      */
    Chromosome* getSolution() const;

    /**
      * @return Simulation duration in seconds
      */
    double getWorkingTimeSec() const;
    /**
      * @return String containing simulation informations, mostly for debugging
      */
    QString getStatisticsString() const;
    /**
      * @return AbstractEnvironment::age
      */
    int getAge() const;
    /**
      * @return AbstractEnvironment::abortedGenerationsCount
      */
    int getAbortedGenerationsCount() const;
    /**
      * Prints environment informations
      */
    friend QDebug operator<<(QDebug dbg, const AbstractEnvironment &env);

signals:
    /**
      * Emited when better solution is found
      * @param QVariant get by Chromosome::data()
      */
    void betterChromosomeFound(QVariant chromosomeData);
    /**
      * Signal containing current simulation progress
      */
    void simulationInfo(int popSize, int age, double bestFit,
                        double worstFit, double sumFit, double workTime);
public slots:
    /**
      * Changes AbstractEnvironment::forceStop to true and stops simulation
      * after current generation processing.
      */
    void stop();

protected:
    /**
      * Constructor setting all variables to default values
      * @param _populationSize Value to set AbstractEnvironment::populationSize
      */
    AbstractEnvironment(int _populationSize);

    /**
      * From QThread
      * Simulation can be run asynchronously using QThread::start()
      * calls AbstractEnvironment::startSimulation()
      */
    void run();
    /**
      * Recreates generation by deleting AbstractEnvironment::currentGeneration
      * and creating new one. Sets AbstractEnvironment::age and
      * AbstractEnvironment::abortedGenerationsCount to zero
      */
    void createPopulation();//TODO change name?
    /**
      * Creates first generation, counts it's fitness and sets
      * AbstractEnvironment::solution to best chromosome from crated generation
      */
    virtual void createPrimaryGenaration();
    /**
      * Checks if AbstractEnvironment::solution is best possible solution
      * comparing to AbstractEnvironment::bestFitness
      * @returns True if AbstractEnvironment::solution is best possible solution, otherwise false
      */
    bool bestChromosomeFound() const;
    /**
      * Counts fitness for all chromosomes in generation and sorts it
      * @param generation Generation to count fitness
      */
    void countFitness(Generation& generation);
    /**
      * Adjusts fitness for all chromosomes in generation to avoid negative values
      * @param generation Generation to adjust
      */
    void adjustFitness(Generation& generation);
    /**
      * Scales fitness for all chromosomes in generation
      * Not used
      * @param generation Generation to scale
      */
    void scaleFitness(Generation& generation);
    /**
      * Removes duplicated chromosomes from generation
      * Some of duplicates can remain in population if
      * randomed new chromosomes will be duplicates of those from generation
      * @param generation Generation to remove duplicates
      */
    void removeDuplicates(Generation& generation);
    /**
      * Adds new chromosomes to generation
      * @param generation Generation to which new chromosomes will be added
      */
    void addFreshChromosomes(Generation& generation);
    /**
      * Counts fitness for chromosome and sets Chromosome::fitness
      * @param chrom Chromosome to count fitness
      */
    virtual void fitnessFunction(Chromosome& chrom) = 0;
    /**
      * @returns True if specified finish condition is reached, otherwise false
      */
    virtual bool finishCondition() const;

    /**
      * Creates new generation, selects chromosomes from current
      * generation using AbstractEnvironment::selectionStrategy
      * Then performs cross over and mutation
      * @return Pointer to created new generation
      */
    Generation* performReproduction();
    /**
      * Replaces AbstractEnvironment::currentGeneration with given generation
      * and increases AbstractEnvironment::age
      * @param newGeneration New generation
      */
    void replaceGeneration(Generation* newGeneration);

    /**
      * Pointer to selection strategy used to select chromosomes from AbstractEnvironment::currentGeneration
      */
    SelectionStrategy* selectionStrategy;
    /**
      * Chromosome factory pointer used to create new chromosomes
      */
    AbstractChromosomeFactory* chromosomeFactory;
    /**
      * Chromosome with best fitness found during all simulation
      */
    Chromosome* solution;
    /**
      * Current generation
      */
    Generation* currentGeneration;

    /**
      * Cross over probability
      */
    double crossoverProb;
    /**
      * Mutation probability
      */
    double mutationProb;
    /**
      * Probability of additional operator 1
      */
    double additionalOperatorProb1;
    /**
      * Probability of additional operator 2
      */
    double additionalOperatorProb2;
    /**
      * Worse generations will be aborted if true
      */
    bool abortWorseGenerations;
    /**
      * Worst chromosome in every generation will be replaced by AbstractEnvironment::solution
      * to increase best chromosome reproduction chances
      */
    bool replaceWorstRepresentative;
    /**
      * Number of new chromosomes added to every generation
      */
    int addFreshChromosomesCount;//TODO check if not greater than pop size
    /**
      * Specifies remove duplicates method to one of AbstractEnvironment::RemoveDuplicatesMethods
      */
    int removeDuplicatesMethod;
    /**
      * Specifies max generation count when AbstractEnvironment::finishConditionType
      * is set to AbstractEnvironment::GenerationCount
      */
    int maxGenerationsCount;
    /**
      * Specifies best fitness when AbstractEnvironment::finishConditionType
      * is set to AbstractEnvironment::BestFitnessKnown
      */
    double bestFitness;
    /**
      * Measure simulation time
      */
    double timeElapsedSec;
    /**
      * Specifies population size
      */
    int populationSize;
    /**
      * Keeps population age
      */
    int age;
    /**
      * Keeps number of aborted generations
      */
    int abortedGenerationsCount;
    /**
      * AbstractEnvironment::adjustFitness() will be called for every generation when true
      * Have to be true for RouletteWheel selection and other selection types
      * which doesn't accept negative values and are sensitive for adding
      * constant value to all chromosomes fitness
      */
    bool performAdjustFitness;
    /**
      * Specifies finish condition type to one of AbstractEnvironment::FinishConditionTypes
      */
    int finishConditionType;
    /**
      * Simulation will stop after current generation processing is finished when true
      */
    bool forceStop;
    /**
      * Specifies how often AbstractEnvironment::simulationInfo() is emited
      */
    int emitFrequency;

private:
    #ifdef LOG_TO_FILE
    void startLogging();
    void logInfo();
    void finishLogging();

    QFile* file;
    QTextStream* stream;
    #endif
};

#endif /*ABSTRACTENVIRONMENT_H_*/
